// --------------------------------------------------------------------------------------------------------------------
// <copyright file="Manifest.cs" company="Microsoft Corporation">
// The MIT License (MIT)
// 
// Copyright (c) 2014, Microsoft Corporation
// 
// Permission is hereby granted, free of charge, to any person obtaining a copy
//  of this software and associated documentation files (the "Software"), to deal
//  in the Software without restriction, including without limitation the rights
//  to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
//  copies of the Software, and to permit persons to whom the Software is
//  furnished to do so, subject to the following conditions:
// 
// The above copyright notice and this permission notice shall be included in
//  all copies or substantial portions of the Software.
// 
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
//  IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
//  FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
//  AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
//  LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
//  OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
//  THE SOFTWARE.
// </copyright>
// --------------------------------------------------------------------------------------------------------------------
namespace Microsoft.Robotics.Runtime
{
    using System;
    using System.Collections.Generic;
    using System.IO;
    using System.Linq;
    using System.Reflection;
    using System.Runtime.Serialization;
    using System.Xml;

    /// <summary>
    /// Manages the persistence of a pipeline of agents. The AgentLocator class uses this class to deserialize manifests.
    /// </summary>
    [DataContract(Name = "root")]
    public sealed class Manifest
    {
        /// <summary>
        /// The names of the agents to activate
        /// </summary>
        [DataMember(Name = "Activation", Order = 1)]
        private string[] activation;

        /// <summary>
        /// The complete set of agents to create
        /// </summary>
        [DataMember(Name = "Agents", Order = 4)]
        private Agent[] agents;

        /// <summary>
        /// The set of proxy definitions representing agents running on other machines.
        /// </summary>
        [DataMember(Name = "RemoteAgents", Order = 2)]
        private ProxyDefinition[] remoteAgentProxies;

        /// <summary>
        /// The set of local agents to make visible over the network.
        /// </summary>
        [DataMember(Name = "NetworkEnabledAgents", Order = 5)]
        private ProxyDefinition[] networkEnabledAgentProxies;

        /// <summary>
        /// Initializes a new instance of the Manifest class
        /// </summary>
        public Manifest()
        {
            this.ActiveAgents = new List<string>();
            this.Agents = new List<Agent>();
            this.RemoteAgentProxies = new List<ProxyDefinition>();
            this.NetworkEnabledAgentProxies = new List<ProxyDefinition>();
            this.OtherKnownTypes = new List<Type>();
        }

        /// <summary>
        /// Gets or sets the names of the agents to explicitly activate.
        /// Most agents don't need to be explicitly activated, because their consumers activate them at the right time.
        /// Thus, in most cases the only agents in this list are the ones with no consumers (i.e. the root agents).
        /// Agent models don't need to be activated, the UI activates them when their view becomes visible. 
        /// The main visualization agent needs to be explicitly activated, but that is best left to the command line.
        /// </summary>
        public List<string> ActiveAgents { get; set; }

        /// <summary>
        /// Gets or sets the complete set of agents to create
        /// </summary>
        public List<Agent> Agents { get; set; }

        /// <summary>
        /// Gets or sets the set of proxy definitions representing agents running on other machines.
        /// </summary>
        public List<ProxyDefinition> RemoteAgentProxies { get; set; }

        /// <summary>
        /// Gets or sets the set of local agents to make visible over the network.
        /// </summary>
        public List<ProxyDefinition> NetworkEnabledAgentProxies { get; set; }

        /// <summary>
        /// Gets or sets the set of known types other than agents and proxies.
        /// </summary>
        public List<Type> OtherKnownTypes { get; set; }

        /// <summary>
        /// Returns the set of types needed to serialize this manifest. Only call this method once the manifest is ready for serialization.
        /// </summary>
        /// <returns>The set of types needed to serialize this manifest</returns>
        public IEnumerable<Type> GetKnownTypes()
        {
            List<Type> types = new List<Type>();
            types.AddRange(this.Agents.Select(a => a.GetType()));
            types.AddRange(this.RemoteAgentProxies.SelectMany(p => p.GetKnownTypes()));
            types.AddRange(this.NetworkEnabledAgentProxies.SelectMany(p => p.GetKnownTypes()));
            types.AddRange(this.OtherKnownTypes);
            return types;
        }

        /// <summary>
        /// Called before serializing this object. 
        /// Validates the manifest and transfers the contents of the lists into arrays for serialization
        /// </summary>
        /// <param name="context">Not used.</param>
        [OnSerializing]
        private void OnSerializing(StreamingContext context)
        {
            this.Validate();
            this.activation = this.ActiveAgents.ToArray();
            this.agents = this.Agents.ToArray();
            this.remoteAgentProxies = this.RemoteAgentProxies.ToArray();
            this.networkEnabledAgentProxies = this.NetworkEnabledAgentProxies.ToArray();
        }

        /// <summary>
        /// Called after the manifest is deserialized.
        /// Populates the lists and validates the data.
        /// </summary>
        /// <param name="context">Not used.</param>
        [OnDeserialized]
        private void OnDeserialized(StreamingContext context)
        {
            this.ActiveAgents = new List<string>(this.activation);
            this.Agents = new List<Agent>(this.agents);
            this.RemoteAgentProxies = new List<ProxyDefinition>(this.remoteAgentProxies);
            this.NetworkEnabledAgentProxies = new List<ProxyDefinition>(this.networkEnabledAgentProxies);

            this.Validate();
        }

        /// <summary>
        /// Validates that the manifest defines a functional pipeline.
        /// Called both when deserializing and when serializing.
        /// </summary>
        private void Validate()
        {
            IEnumerable<string> allNames = this.Agents.Select(a => a.Name);
            if (this.RemoteAgentProxies != null)
            {
                allNames = allNames.Concat(this.RemoteAgentProxies.Select(r => r.Name));
            }
            
            foreach (ProxyDefinition netProxy in this.NetworkEnabledAgentProxies)
            {
                if (!allNames.Contains(netProxy.Producer))
                {
                    throw new Exception("To enable recording of an agent's output you need to first add the agent to the agents set");
                }
            }

            foreach (Agent agent in this.Agents)
            {
                if (allNames.Count(name => name == agent.Name) > 1)
                {
                    throw new Exception(string.Format("There is more than one agent or proxy with the name {0}", agent.Name));
                }
            }
        }
    }
}
